package MineSweeper;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Vector;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;

public class Game implements MouseListener,MouseMotionListener{
	// main frame
	JFrame frame;
	// tow panels, one for play area, another for time
	JPanel minearea, timeborder;
	// main menu bar
	JMenuBar menuBar;
	// sub menus
	JMenu gamemenu,helpmenu,option;
	// menu items
	JMenuItem newgame, highscore, exit, viewhelp;
	// radio button for different difficulties
	JRadioButtonMenuItem beginner, intermediate, advanced;
	ButtonGroup difficulty;
	JButton but1,but2;
	// label to show the number of mines remained
	JLabel mineLabel;
	// game settings, stands for length width and mine number
	int length, width, Mnumber;
	// record current position, -1 for initialize.
	// It is used for locate buttons
	int currentPosition = -1;
	// show current buttons' states with painting method
	// it stands for mouse state:
	// neither of two buttons are pressed: 0
	// button1 is pressed, however, button 3 is not: 1
	// button1 is not, however, button 3 is pressed: 3
	// both are pressed: 4
	int mousestate = 0;
	// the number of mines remained
	int mineRem;
	// time
	int time = 0;
	MyButton[] mines;
	// a mine map
	MineMap mm;
	// used for findall method
	Vector<Integer> stored = new Vector<Integer>();
	// record time
	Timer timer;
	// time label
	JLabel timeLabel;
	
	/**
	 * constructor 
	 * @param length, length of the grid
	 * @param width, width of the grid
	 * @param Mnumber, the number of mines
	 */
	public Game(int length, int width, int Mnumber){
		// look and feel
		try {
			for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
				if ("Nimbus".equals(info.getName())) {
					UIManager.setLookAndFeel(info.getClassName());
					break;
				}
			}
		} catch (Exception e) {

		}
		// set variables
		this.length = length;
		this.width = width;
		this.Mnumber = Mnumber;
		this.mm = new MineMap(length, width, Mnumber);
		this.mineRem = Mnumber;
		mm.printMap();
		// set a new frame called MineGame
		frame = new JFrame("MineGame");
		// set close operation
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		// size
		//frame.setSize(600,500);
		// set icon image
		frame.setIconImage(new ImageIcon("./button/1.jpg").getImage());
		// create a menu bar
		menuBar = new JMenuBar();
		// build menus
		gamemenu = new JMenu("Game");
		helpmenu = new JMenu("Help");
		gamemenu.setMnemonic(KeyEvent.VK_A);
		helpmenu.setMnemonic(KeyEvent.VK_H);
		menuBar.add(gamemenu);
		menuBar.add(helpmenu);
		
		// build menu items
		newgame = new JMenuItem("New Game", KeyEvent.VK_N);
		option = new JMenu("Option");
		highscore = new JMenuItem("Highscore", KeyEvent.VK_S);
		exit = new JMenuItem("Exit",KeyEvent.VK_X);
		newgame.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0));
		option.setMnemonic(KeyEvent.VK_O);
		highscore.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F6, 0));
		exit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, ActionEvent.ALT_MASK));
		viewhelp = new JMenuItem("View Help", KeyEvent.VK_V);
		viewhelp.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0));
		
		// Build JRadio Button
		difficulty = new ButtonGroup();
		beginner = new JRadioButtonMenuItem("Beginner");
		beginner.setSelected(true);
		beginner.setMnemonic(KeyEvent.VK_B);
		intermediate = new JRadioButtonMenuItem("Intermediate");
		intermediate.setSelected(false);
		intermediate.setMnemonic(KeyEvent.VK_M);
		advanced = new JRadioButtonMenuItem("Advanced");
		advanced.setSelected(false);
		advanced.setMnemonic(KeyEvent.VK_D);
		
		// Group together
		difficulty.add(beginner);
		difficulty.add(intermediate);
		difficulty.add(advanced);
		option.add(beginner);
		option.add(intermediate);
		option.add(advanced);
		
		// Add action listeners
		newgame.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e) {
				restart();
			}
		});
		beginner.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e) {
				changesetting(9,9,10);
				restart();
			}
		});
		intermediate.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e) {
				changesetting(16,16,40);
				restart();
			}
		});
		advanced.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e) {
				changesetting(30,16,99);
				restart();
			}
		});
		highscore.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e) {
				String filename = "";
				Object[] options={"EASY","MODERATE","HARD", "RESET"};
				int respone=JOptionPane.showOptionDialog(null,"You can choose the difficulty to see high scores","High scores for three difficulties",
								JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE,null,options,options[0]);
				if( respone != 3){
					if (respone == 0)
					{
						filename = "highScore_EASY.txt";
					}
					if (respone == 1){
						filename = "highScore_MODERATE.txt";
					}
					if (respone == 2){
						filename = "highScore_HARD.txt";
					}
					String message = "";
					try {
						BufferedReader br = new BufferedReader(new FileReader(
								filename));
						String line;
						while ((line = br.readLine()) != null) {
							message = message + line + "\n";
						}
					} catch (IOException ioe) {
					}
					JOptionPane.showMessageDialog(null, message, "High Score",
							JOptionPane.INFORMATION_MESSAGE);
				}else{
					try{
						String message = "";
						for(int i = 0; i < 10; i++){
							message = message + (i+1) + "," + "abc" + "," + 999 + "\n";
						}
						BufferedWriter bw = new BufferedWriter(new FileWriter(
								"highScore_EASY.txt"));
						bw.write(message);
						bw.close();
						bw = new BufferedWriter(new FileWriter(
						"highScore_MODERATE.txt"));
						bw.write(message);
						bw.close();
						bw = new BufferedWriter(new FileWriter(
						"highScore_HARD.txt"));
						bw.write(message);
						bw.close();
						JOptionPane.showMessageDialog(null, "Reset high score", "High Score",
								JOptionPane.INFORMATION_MESSAGE);

					}catch(Exception ee){
					}
				}
			}
		});
		exit.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e) {
				System.exit(0);
				
			}
		});
		viewhelp.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e) {
				System.out.println("Viewhelp");
			}
		});
		
		// Add menus and items
		gamemenu.add(newgame);
		gamemenu.addSeparator();
		gamemenu.add(option);
		gamemenu.add(highscore);
		gamemenu.addSeparator();
		gamemenu.add(exit);
		helpmenu.add(viewhelp);
		frame.setJMenuBar(menuBar);
		
		// Add panel
		mines = new MyButton[length * width];
		// set layout
		frame.setLayout(new BorderLayout());
		minearea = new JPanel(new GridLayout(width, length));
		// initialize buttons
		for(int i = 0; i < length*width; i++){
			String s = String.valueOf(i);
			// initial value -2, stands for not pressed
			mines[i] = new MyButton(-2);
			// add buttons to the panel
			minearea.add(mines[i]);
			mines[i].setContentAreaFilled(false);
			//set size
			mines[i].setSize(24,24);
			//use i to set name
			mines[i].setName(s);
			//add mouse listener
			mines[i].addMouseListener(this);
			mines[i].addMouseMotionListener(this);
		}
		// time border
		//but2 = new JButton("time border");
		mineLabel = new JLabel("Mines left: " + mineRem);
		timeLabel = new JLabel("Time: " + time);
		timer = new Timer(1000, (new ActionListener(){
			public void actionPerformed(ActionEvent e) {
				time++;
				if(time > 999){
					time = 999;
				}
				timeLabel.setText("Time " + time);	
			}
		}));
		timeborder= new JPanel();
		timeborder.add(mineLabel);
		timeborder.add(timeLabel);
		// add two border to the frame
		frame.add(timeborder, BorderLayout.SOUTH);
		frame.add(minearea, BorderLayout.CENTER);
		// set size
		frame.setSize(28*width,37*length);
		// can not change the size of the frame
		frame.setResizable(false);
		// set visible
		frame.setVisible(true);
	}

	private void restart(){
		timer.stop();
		time = 0;
		timeLabel.setText("Time " + time);	
		mm = new MineMap(length, width, Mnumber);
		mineRem = Mnumber;
		mineLabel.setText("Mines left: " + mineRem);
		minearea.removeAll();
		minearea.invalidate();
		minearea.setVisible(false);
		minearea.setLayout(new GridLayout(width, length));
		// initialize buttons
		for(int i = 0; i < length*width; i++){
			String s = String.valueOf(i);
			// initial value -2, stands for not pressed
			mines[i] = new MyButton(-2);
			// add buttons to the panel
			minearea.add(mines[i]);
			mines[i].setContentAreaFilled(false);
			//set size
			mines[i].setSize(24,24);
			//use i to set name
			mines[i].setName(s);
			//add mouse listener
			mines[i].addMouseListener(this);
			mines[i].addMouseMotionListener(this);
		}
		minearea.validate();
		frame.setSize(28*length,37*width);
		minearea.setVisible(true);
		mousestate = 0;
		currentPosition = -1;
	}
	
	/**
	 * mouse clicked
	 */
	public void mouseClicked(MouseEvent e) {}
	/**
	 * mouse entered
	 */
	public void mouseEntered(MouseEvent e) {
		// get the button name
		int buttonNum =  Integer.parseInt(e.getComponent().getName());
		// set current position, using this button name.
		currentPosition = buttonNum; 
		// when left button is pressed or both buttons are pressed
		if(mousestate == 1 || mousestate == 4){
			if(mines[buttonNum].getType() == -2){
				mines[buttonNum].setType(-4);
			}else if(mines[buttonNum].getType() == - 6){
				mines[buttonNum].setType(-8);
			}
		}
		// when right button is pressed
		if(mousestate == 3){
			if(mines[buttonNum].getType() == -2){
				mines[buttonNum].setType(-3);
			}else if(mines[buttonNum].getType() ==-1){
				mines[buttonNum].setType(-5);
			}else if(mines[buttonNum].getType() == -6){
				mines[buttonNum].setType(-7);
			}
		}
	}
	/**
	 * mouse exit
	 */
	public void mouseExited(MouseEvent e) {
		// get the button name
		int buttonNum =  Integer.parseInt(e.getComponent().getName());
			// if the button are painted as pressed or overstate
			if((mines[buttonNum].getType() == -4) || (mines[buttonNum].getType() == -3)){
				mines[buttonNum].setType(-2);
			}else{
				// if flag overstate
				if(mines[buttonNum].getType() == -5){
					mines[buttonNum].setType(-1);
				}
				// if unknown overstate
				if((mines[buttonNum].getType() == -7) || (mines[buttonNum].getType() == -8)){
					mines[buttonNum].setType(-6);
				}
			}
	}
	/**
	 * mouse pressed
	 */
	public void mousePressed(MouseEvent e) {
		// left mouse pressed
		if(e.getModifiers() == InputEvent.BUTTON1_MASK){
			int buttonNum =  Integer.parseInt(e.getComponent().getName());
			if((mines[buttonNum].getType() == -3) || (mines[buttonNum].getType()== -7)){
				mines[buttonNum].setType(-4);
			}
			mousestate = mousestate +1;
		}
		// left mouse pressed
		if(e.getModifiers() == InputEvent.BUTTON3_MASK){
			int buttonNum =  Integer.parseInt(e.getComponent().getName());
			if(mines[buttonNum].getType() == -3){
				mines[buttonNum].setType(-5);
				mineRem --;
				mineLabel.setText("Mines left: "+ mineRem);
			}else if(mines[buttonNum].getType() == -5){
				mines[buttonNum].setType(-7);
				mineRem ++;
				mineLabel.setText("Mines left: "+ mineRem);
			}else if(mines[buttonNum].getType() == -7){
				mines[buttonNum].setType(-3);
			}
			mousestate = mousestate + 3;
			if(haswin()){
				this.finished();
				if(Mnumber == 10){
					checkHighscore(time, "highScore_EASY.txt");
				}else if (Mnumber == 40){
					checkHighscore(time, "highScore_MODERATE.txt");
				}else if (Mnumber == 99){
					checkHighscore(time, "highScore_HARD.txt");
				}
				JOptionPane.showMessageDialog(null, "you win!", "you win",JOptionPane.QUESTION_MESSAGE);
			}
		}
	}
	/**
	 * mouse released
	 */
	public void mouseReleased(MouseEvent e) {
		if(mines[currentPosition].getType() == -4){
			if(mousestate == 4){
					mines[currentPosition].setType(-3);
			}
			if(mousestate == 1){
				timer.start();
				if (mm.getLocation(currentPosition) == -1){
					mines[currentPosition].setType(-9);
					for(int i = 0; i < length * width; i++){
						if(mm.getLocation(i) == -1){
							if((mines[i].getType() == -1) || (mines[i].getType() == -5)){
								mines[i].setType(-11);
							}else if((mines[i].getType() == -2) || (mines[i].getType() == -3)){
								mines[i].setType(-10);
							}
						}
					}
					this.finished();
					JOptionPane.showMessageDialog(null, "you lose!", "you lose",JOptionPane.INFORMATION_MESSAGE);
				}else{
					mines[currentPosition].setType(mm.getLocation(currentPosition));
					if(mm.getLocation(currentPosition) == 0){
						findall(currentPosition);
					}
					if(haswin()){
						this.finished();
						if(Mnumber == 10){
							checkHighscore(time, "highScore_EASY.txt");
						}else if (Mnumber == 40){
							checkHighscore(time, "highScore_MODERATE.txt");
						}else if (Mnumber == 99){
							checkHighscore(time, "highScore_HARD.txt");
						}
						JOptionPane.showMessageDialog(null, "you win!", "you win",JOptionPane.QUESTION_MESSAGE);
					}
				}
			}
			if(mousestate == 3){
				if(haswin()){
					this.finished();
					if(Mnumber == 10){
						checkHighscore(time, "highScore_EASY.txt");
					}else if (Mnumber == 40){
						checkHighscore(time, "highScore_MODERATE.txt");
					}else if (Mnumber == 99){
						checkHighscore(time, "highScore_HARD.txt");
					}
					this.finished();
					if(Mnumber == 10){
						checkHighscore(time, "highScore_EASY.txt");
					}else if (Mnumber == 40){
						checkHighscore(time, "highScore_MODERATE.txt");
					}else if (Mnumber == 99){
						checkHighscore(time, "highScore_HARD.txt");
					}
					JOptionPane.showMessageDialog(null, "you win!", "you win",JOptionPane.QUESTION_MESSAGE);
					
				}
			}
		}else{
			if(mousestate == 4){
				if(mines[currentPosition].getType() >= 0){
					if(checkAround(currentPosition)){
						findall(currentPosition);
						if(!checkAround(currentPosition)){
								for(int i = 0; i < length * width; i++){
									if(mm.getLocation(i) == -1){
										if((mines[i].getType() == -1) || (mines[i].getType() == -5)){
											mines[i].setType(-11);
										}else if((mines[i].getType() == -2) || (mines[i].getType() == -3)){
											mines[i].setType(-10);
										}
									}
								}
								this.finished();
								JOptionPane.showMessageDialog(null, "you lose!", "you lose",JOptionPane.INFORMATION_MESSAGE);
						}
					}
				}
				if(haswin()){
					this.finished();
					if(Mnumber == 10){
						checkHighscore(time, "highScore_EASY.txt");
					}else if (Mnumber == 40){
						checkHighscore(time, "highScore_MODERATE.txt");
					}else if (Mnumber == 99){
						checkHighscore(time, "highScore_HARD.txt");
					}
					JOptionPane.showMessageDialog(null, "you win!", "you win",JOptionPane.QUESTION_MESSAGE);
				}
			}
		}
		// detect the error that click mouse in other area and release at the minearea
		if(mousestate != 0){
			mousestate = mousestate - e.getButton();
		}
	}
	/**
	 * mouse dragged
	 */
	public void mouseDragged(MouseEvent e) {}
	/**
	 * mouse moved
	 */
	public void mouseMoved(MouseEvent e) {
		int buttonNum =  Integer.parseInt(e.getComponent().getName());
		if(mines[buttonNum].getType() == -2){
			mines[buttonNum].setType(-3);
		}
		if(mines[buttonNum].getType() == -1){
			mines[buttonNum].setType(-5);
		}
		if(mines[buttonNum].getType() == -6){
			mines[buttonNum].setType(-7);
		}
	}
	/**
	 * set the game state to finished
	 */
	private void finished(){
		timer.stop();
		// remove actionlistener from buttons
		for(int i = 0; i < length * width; i++){
			mines[i].removeMouseListener(this);
			mines[i].removeMouseMotionListener(this);
		}
		mousestate = 0;
	}
	/**
	 * paint connected buttons which are not mines
	 * @param n
	 */
	private void findall(int n){	
			// left
			if(n % length > 0 && (mines[n-1].getType() == -2)){
				mines[n-1].setType(mm.getLocation(n-1));
				if(mm.getLocation(n-1) == 0){
					stored.add(n-1);
				}
			}
			// left up
			if((n % length > 0) && (n >= length) && (mines[n-length-1].getType() == -2)){
				mines[n-length-1].setType(mm.getLocation(n-length-1));
				if(mm.getLocation(n-length-1) == 0){
					stored.add(n-length-1);
				}
			}
			// up
			if((n >= length)&& (mines[n-length].getType() == -2)){
				mines[n-length].setType(mm.getLocation(n-length));
				if(mm.getLocation(n-length) == 0){
					stored.add(n-length);
				}
			}
			// right up
			if((n >= length) && ((n+1) % length != 0)&& (mines[n-length+1].getType() == -2)){
				mines[n-length+1].setType(mm.getLocation(n-length+1));
				if(mm.getLocation(n-length+1) == 0){
					stored.add(n-length+1);
				}
			}
			// right
			if(((n+1) % length != 0) && (mines[n+1].getType() == -2)){
				mines[n+1].setType(mm.getLocation(n+1));
				if(mm.getLocation(n+1) == 0){
					stored.add(n+1);
				}
			}
			// right down
			if(((n+1) % length != 0) && (n < (width-1)*length) && (mines[n+length+1].getType() == -2)){
				mines[n+length+1].setType(mm.getLocation(n+length+1));
				if(mm.getLocation(n+length+1) == 0){
					stored.add(n+length+1);
				}
			}
			// down
			if((n < (width-1)*length) && (mines[n+length].getType() == -2)){
				mines[n+length].setType(mm.getLocation(n+length));
				if(mm.getLocation(n+length) == 0){
					stored.add(n+length);
				}
			}
			// left down
			if((n < (width-1)*length) && (n % length > 0) && (mines[n+length-1].getType() == -2)){
				mines[n+length-1].setType(mm.getLocation(n+length-1));
				if(mm.getLocation(n+length-1) == 0){
					stored.add(n+length-1);
				}
			}
			while(!stored.isEmpty()){
				int temp = stored.get(0);
				stored.remove(0);
				findall(temp);
			}
	}
	/**
	 * if the player has won
	 * @return true if player wins, no otherwise
	 */
	private boolean haswin(){
		boolean temp = true;
		for(int i = 0; i < length*width; i++){
			if(mm.getLocation(i) >= 0){
				temp = temp && (mines[i].getType() == mm.getLocation(i));
			}else{
				if((mines[i].getType() == -5) || (mines[i].getType() == -1)){
					temp = temp && true;
				}else{
						return false;
				}
			}
		}
		return temp;
	}

	private boolean checkAround(int n){
		int count = 0;
		int mineN = mm.getLocation(n);
		//left
		if((n % length > 0) && (mines[n-1].getType() == -1)){
			count++;
		}
		//left up
		if((n % length > 0) && (n >= length) && (mines[n-length-1].getType() == -1)){
			count++;
		}
		//up
		if((n >= length)&& (mines[n-length].getType() == -1)){
			count++;
		}
		//right up
		if((n >= length) && ((n+1) % length != 0)&& (mines[n-length+1].getType() == -1)){
			count++;
		}
		//right
		if(((n+1) % length != 0) && (mines[n+1].getType() == -1)){
			count++;
		}
		//right down
		if(((n+1) % length != 0) && (n < (width-1)*length) && (mines[n+length+1].getType() == -1)){
			count++;
		}
		//down
		if((n < (width-1)*length) && (mines[n+length].getType() == -1)){
			count++;
		}
		//left down
		if((n < (width-1)*length) && (n % length > 0) && (mines[n+length-1].getType() == -1)){
			count++;
		}
		return (count == mineN);
	}

	private void changesetting(int length, int width, int Mnumber){
		this.length = length;
		this.width = width;
		this.Mnumber = Mnumber;
		mines = new MyButton[length*width];
	}
	
	/**
	 * Check if the current score is higher than the scores which is hold in highscore
	 */
	private void checkHighscore(int score, String filename){
		ArrayList<String> nameHolder = new ArrayList<String>();
		ArrayList<Integer> scoreHolder = new ArrayList<Integer>();
		String[] tempHolder = new String[3];
		nameHolder.ensureCapacity(10);
		scoreHolder.ensureCapacity(10);
		String message = "";
		try {
			BufferedReader br = new BufferedReader(new FileReader(
					filename));
			String line;
			while ((line = br.readLine()) != null) {
				tempHolder = line.split(",",3);
				nameHolder.add(tempHolder[1]);
				scoreHolder.add(Integer.parseInt(tempHolder[2]));
			}
			if(scoreHolder.get(9) > score){
				String name = JOptionPane.showInputDialog("your name");
				try{
					while(name.contains(",")){
						JOptionPane.showMessageDialog(null, "Name can not contain character ',' ", "Format Error",
								JOptionPane.INFORMATION_MESSAGE);
						name = JOptionPane.showInputDialog("your name");
					}
					int n = 9;
					while((n >0) && (scoreHolder.get(n-1) > score)){
						n--;
					}
					scoreHolder.add(n, score);
					nameHolder.add(n, name);
					nameHolder.remove(10);
					scoreHolder.remove(10);
					for(int i = 0; i < scoreHolder.size(); i++){
						message = message + (i+1) + "," + nameHolder.get(i) + "," + scoreHolder.get(i) + "\n";
					}
					JOptionPane.showMessageDialog(null, message, "High Score",
							JOptionPane.INFORMATION_MESSAGE);
					BufferedWriter bw = new BufferedWriter(new FileWriter(
							filename));
					bw.write(message);
					bw.close();
				}catch(Exception e){

				}
			}
		} catch (IOException ioe) {
		}
	}
}
